#!/usr/bin/env python3

"""This script is used to sync gitlab group to another gitlab"""

import logging
import os
import sys

import git
import tempfile

from common.init_argparse import parse_sys_args
from common.init_logging import init_logging
from common.git_repo import GitRepo
from common.gitlab_wrapper import GitlabWrapper


def sync(args):
    local_group = args.local_group
    local_gl = GitlabWrapper(args.local, args.local_token)

    remote_group = args.remote_group
    remote_gl = GitlabWrapper(args.remote, args.remote_token)

    # force push
    force_push = args.force_push
    push_url = args.push_url

    # Get branches arguments
    ignore_branches = []
    if args.ignore_branches:
        ignore_branches = args.ignore_branches.split(",")

    allow_branches = []
    if args.allow_branches:
        allow_branches = args.allow_branches.split(",")

    # NOTE(Ray): Support multiple groups sync, you can specify different sync
    # group target, but need to keep the same numbers in remote group, or just
    # leave as blank, we will use the same name as the local group name
    # UPDATE(2023-04-03): We will sync all local groups to remote if local group
    # is empty
    if local_group:
        local_groups = local_group.split(",")
    else:
        local_groups = [g.full_path for g in local_gl.groups]

    if not remote_group:
        remote_groups = local_groups
    else:
        remote_groups = remote_group.split(",")

    if not len(remote_groups) == len(local_groups):
        raise Exception("Not enough remote groups given, "
                        "local groups: %s, remote_groups: %s" % (
                            local_groups, remote_groups))

    for index, lg in enumerate(local_groups):
        logging.debug("Working on local grooup %s..." % lg)

        rg = remote_groups[index]

        if not local_gl.is_group_exists(lg):
            raise Exception(
                "Can not find local group name %s, "
                "avaliabe group names are: %s" % (
                    lg, local_gl.group_names()))

        local_projects = local_gl.group_projects(lg)

        # NOTE(Ray): Add parent group for syncing, all remote groups will be saved
        # into this namespace
        parent_group = args.remote_parent_group

        _sync_group_projects(local_projects,
                             remote_gl,
                             rg,
                             push_url,
                             ignore_branches,
                             allow_branches,
                             force_push,
                             parent_group)


def _sync_group_projects(local_projects,
                         remote_gl,
                         remote_group,
                         push_url,
                         ignore_branches,
                         allow_branches,
                         force_push,
                         parent_group=None):
    with tempfile.TemporaryDirectory() as tmpdirname:
        # Get all local projects in group, pull code from
        # all branches, then push to remote
        for p in local_projects:
            logging.debug("Local project info: %s" % p)
            project_path = p.path
            git_url = p.ssh_url_to_repo
            local_namespace = p.namespace["full_path"]
            remote_namespace = _get_remote_namespace(
                local_namespace, remote_group, parent_group)
            remote_url = os.path.join(
                push_url, remote_namespace, project_path)
            src_path = os.path.join(
                tmpdirname, local_namespace, project_path)

            if not os.path.exists(src_path):
                os.makedirs(src_path)

            # Download specific repo with all branches
            logging.info("Run git clone %s to %s" % (
                git_url, src_path))
            local_repo = GitRepo(git_url, src_path)
            local_repo.clone()
            for branch in local_repo.branches:
                if not _is_sync_branch(ignore_branches,
                                       allow_branches,
                                       branch):
                    logging.info("Found ignore branch %s, skip to pull" % branch)
                    continue
                if not branch == local_repo.current_branch:
                    logging.info("Pulling branch %s..." % branch)
                    local_repo.pull_branch(branch)

            # Make sure groups and projects are created in remote gitlab
            remote_gl.ensure_project_exists(
                remote_namespace, project_path)

            # Push all branches and tags to remote
            logging.info("Run git push to %s..." % remote_url)
            remote_repo = GitRepo(remote_url, src_path)
            remote_repo.push_all_branches(force=force_push)
            remote_repo.push_all_tags(force=force_push)


def _is_sync_branch(ignore_branches, allow_branches, branch_name):
    """Return True if branch need to be sync

    Ignore branch priority is higher than allow branch, loop all ignore
    branch first than find allow branches
    """
    for ib in ignore_branches:
        if ib in branch_name:
            return True

    # if allow branches is empty, by default, we will sync all branches
    if allow_branches:
        for ab in allow_branches:
            if ab in branch_name:
                return True

        # Return false if we can't find name in allow branches
        return False
    else:
        return True


def _get_remote_namespace(local_namespace, remote_group, parent_group=None):
    """Replace the first group name with remote group"""
    namespaces = local_namespace.split("/")
    namespaces[0] = remote_group

    # Add parent group before sycing if remote-parent-group is given
    if parent_group:
        namespaces.insert(0, parent_group)

    return "/".join(namespaces)


def main():
    args = parse_sys_args(sys.argv)
    init_logging(verbose=args.verbose, debug=args.debug)

    sync(args)


if __name__ == "__main__":
    main()
